/**
 * redux 提供的方法： 
 *  1. createStore：返回 store 对象，拥有 getStore(), dispatch(action), subscribe(listener) 三种方法
 *  2. combineReducers： 将多个reducer 合并成一个最终的reducer
 *  3. bindActionCreators： 把 action creators 转换成 同key的对象，只是 key 对应的 value 变成了 dispatch(val) 的函数
 *  4. applyMiddleware：扩展增强 redux。
 */
// 
// 返回值 { getStore, dispatch, subscribe }

/**
 * 创建一个 Redux store 来以存放应用中所有的 state
 * @param {function} reducer 接收两个参数，分别是当前的 state 树和要处理的 action，返回新的 state 树
 * @param {any} preloadedState 初始时的 state
 * @param {function} enhancer 一个组合 store creator 的高阶函数，返回一个新的强化过的 store creator，与 middleware 类似
 * @return {store}
 */
function createStore(reducer, preloadedState, enhancer) {
    if(typeof reducer !== 'function') throw new Error('reducer 必须是函数')

    if(enhancer !== undefined) {
        if(typeof enhancer !== 'function') {
            throw new Error('enhancer 必须是函数')
        }
        return enhancer(createStore)(reducer, preloadedState)
    }

    let currentState = preloadedState

    let listeners = []

    const getStore = () => currentState

    const dispatch = action => {
        if(!isPlainObject(action)) throw new Error('action 必须是 plainObject')
        if(action.type === undefined) throw new Error('action 必须有 type 属性')
        
        currentState = reducer(currentState, action)

        listeners.forEach(listener => listener())
    }

    const subscribe = listener => {
        listeners.push(listener)
        // 返回函数，注销监听器
        return function() {
            const index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }

    // 默认调用一次 dispatch， 存储 reducer 传递的初始状态
    dispatch({type:'initAction'})

    return {
        getStore,
        dispatch,
        subscribe
    }
}

/**
 * 判断是否为普通对象（非数组）
 * @param {*} obj 
 * @return {boolean}
 */
function isPlainObject(obj) {
    if(typeof obj !== 'object' || obj === null) return false
    let proto = obj
    while(Object.getPrototypeOf(proto)) {
        proto = Object.getPrototypeOf(proto)
    }
    return proto === Object.getPrototypeOf(obj)
}

/**
 * 把一个由多个不同 reducer 函数作为 value 的 object，合并成一个最终的 reducer 函数
 * @param {object} reducers { key1: reducer1, key2: reducer2}
 * @return {function}
 */
function combineReducers(reducers) {
    for(let key in reducers) {
        if(typeof reducers[key] !== 'function') throw new Error('reducer 必须是函数')
    }
    // 调用每个小的reducer, 将每个小的reducer返回的状态保存到一个新的state中
    return function(state, action) {
        const nextState = {}
        for(let key in reducers) {
            const reducer = reducers[key]
            const preStateOfKey = state[key]
            nextState[key] = reducer(preStateOfKey, action)
        }
        return nextState
    }
}

/**
 * 把一个 value 为不同 action creator 的对象，转成拥有同名 key 的对象。只是 key 对应的 value 变成了 dispatch(value) 的函数
 * @param {function|object} actionCreactors 
 * @param {function} dispatch store.dispatch
 * @return {function|object}
 */
function bindActionCreators(actionCreactors, dispatch) {
    // 如果第一个参数是函数，返回一个函数
    if(typeof actionCreactors === 'function') {
        return (payload) => dispatch(actionCreactors(payload))
    }
    const creators = {}
    for(let key in actionCreactors) {
        creators[key] = (payload) => {
            dispatch(actionCreactors[key](payload))
        }
    }
    return creators
}

/**
 * applyMiddleware 与 enhancer 类似
 * @param  {arguments} middlewares
 * @return  {function}
 */
function applyMiddleware(...middlewares) {
    return function(createStore) {
        return function(reducer, preloadedState) {
            const store = createStore(reducer, preloadedState)
            const chain = middlewares.map(middleware => middleware(store))
            const dispatch = compose(...chain)(store.dispatch)
            return {
                ...store,
                dispatch
            }
        }
    }
}

/**
 * compose 从右到左组合多个函数  compose(funcA, funcB, funcC)  转换为  compose(funcA(funcB(funcC()))) 
 * @param  {arguments} fns 
 * @return  {function}
 */
function compose(...fns) {
    return function (dispatch) {
        for(let i = fns.length - 1; i >= 0; i--){
            dispatch = fns[i](dispatch)
        }
        return dispatch
    }
}




